/* Copyright © 2010 LPRM
Universidade Federal do Espírito Santo - RNP-GT CWTools

This file is part of GW-Android.

GW-Android is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

GW-Android is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with GW-Android. If not, see <http://www.gnu.org/licenses/>. */
package br.ufes.cwtools.gw.android;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.ConnectException;

import org.apache.commons.io.IOUtils;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.database.Cursor;
import android.database.sqlite.SQLiteCursor;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.SimpleCursorAdapter;
import android.widget.Spinner;
import android.widget.Toast;
import br.ufes.cwtools.gw.android.components.Component;
import br.ufes.cwtools.gw.android.connection_service.ConnectionService;
import br.ufes.cwtools.gw.android.connection_service.ConnectionServiceConnection;
import br.ufes.cwtools.gw.android.connection_service.IConnectionService;

public class GroupwareClient extends Activity {
	private static final String LOG_TAG = "GroupwareClient";

	private static final String GW_WEBAPP_SUFIX = "/GW-Application-Arquigrafia";
	private static final String GW_APPDATA_SUFIX = "/groupware-workbench/android/applicationDefinition";
	
	private static final String PREFERENCE_USERNAME = "username";
	private static final String PREFERENCE_PASSWORD = "password";
	private static final String PREFERENCE_SERVER = "server";
	
	private static final int ACTION_ADD_SERVER = 1;
	
	private ServersDbAdapter mDbAdapter;
	private Spinner mSpinner;
	
	private ProgressDialog mConnectDialog = null;
	private String xmlRequestCode;

	private String loginValidatorRequestCode;
	private LinearLayout mainLayout;

	private String appXml;
	private String serverName, serverUrl, serverXmlUrl;
	private SharedPreferences prefs;
	private String[] usernames;
	private String[] passwords;
	private int currentServer;
	
    private Handler gcHandler;
    private final int MSG_TOAST = 101;
    private final int MSG_DISMISS_DIALOG = 102;
    private final int MSG_UPDATE_DIALOG = 103;
    private final int MSG_FINISH = 104;
    
    private boolean creatingServer = false;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    	Log.v(LOG_TAG, "onCreate()");
    	super.onCreate(savedInstanceState);
        
        //Setting up the layout
        setContentView(R.layout.server_selection);
        mainLayout = (LinearLayout) findViewById(R.id.main_layout);
        updateBackground();

        //Load preferences
        prefs = getPreferences(MODE_PRIVATE);
        
        //Message Handlers
        gcHandler = new Handler(){
        	@Override
        	public void handleMessage(Message msg) {
        		switch(msg.what){
        			case MSG_TOAST:
        				Toast.makeText(GroupwareClient.this, (String)msg.obj, msg.arg1).show();
        				break;
        			case MSG_DISMISS_DIALOG:
        				dismissTaskDialog();
        				break;
        			case MSG_UPDATE_DIALOG:
        				if (mConnectDialog != null){
            				mConnectDialog.setMessage((String)msg.obj);
            			}
        				break;
        			case MSG_FINISH:
        				onTaskCompleted();
        				break;
        		}
        	}
        };
        
        mDbAdapter = new ServersDbAdapter(this);
        mDbAdapter.open();
        fillData();
        
        //Restoring settings
        usernames = prefs.getString(PREFERENCE_USERNAME, "").split("\b");
        passwords = prefs.getString(PREFERENCE_PASSWORD, "").split("\b");
        currentServer = prefs.getInt(PREFERENCE_SERVER, 0);

        int numServers = mSpinner.getCount();
        if(numServers != usernames.length && numServers != 0){
        	Log.e(LOG_TAG, "ERRO no tamanho das preferências/número de servidores.");
        	usernames = new String[numServers];
        	passwords = new String[numServers];
        }

        if(mSpinner.getCount() > currentServer){
    		mSpinner.setSelection(currentServer);
            ((EditText) findViewById(R.id.login)).setText(usernames[currentServer]);
        	((EditText) findViewById(R.id.password)).setText(passwords[currentServer]);
    	}
        
        bindService(new Intent(IConnectionService.class.getName()), mLoginConnection, Context.BIND_AUTO_CREATE);
    }
    
    @Override
    protected void onResume() {
    	super.onResume();
    	
    	if(!isServerListEmpty() && !creatingServer){
    		mSpinner.setSelection(currentServer);
            ((EditText) findViewById(R.id.login)).setText(usernames[currentServer]);
        	((EditText) findViewById(R.id.password)).setText(passwords[currentServer]);
    	}
    	
    	creatingServer = false;
    }
    
    private void updateBackground(){
    	mainLayout.setBackgroundResource(0);
    	
    	if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
    		mainLayout.setBackgroundResource(R.drawable.fundo_gw);
    	}
    }
    
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
    	updateBackground();
    	super.onConfigurationChanged(newConfig);
    }
    
    private boolean isServerListEmpty(){
    	if(mSpinner.getCount() == 0){
			final Intent intent = new Intent(this, AddServerActivity.class);
			startActivityForResult(intent, ACTION_ADD_SERVER);
			return true;
		}
    	return false;
    }
    
    @Override
    protected void onDestroy() {
    	Log.v(LOG_TAG, "onDestroy()");
    	super.onDestroy();
    	mDbAdapter.close();
    	dismissTaskDialog();
    	unbindService(mLoginConnection);
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    	super.onActivityResult(requestCode, resultCode, data);
    	
    	if (requestCode == ACTION_ADD_SERVER && resultCode == RESULT_OK) {
    		refreshSpinner();
    		//Seleciona o servidor recém adicionado
    		mSpinner.setSelection(mSpinner.getCount()-1);
    		//Se acabou de inserir o primeiro servidor
    		//não precisa fazer nada, pois o default
    		//desses vetores é serem unitários com uma string vazia
    		if (mSpinner.getCount() > 1){
    			usernames = addToArray(usernames, "");
    			passwords = addToArray(passwords, "");
    		}
    		//Servidor novo, os dados ainda não estão salvos
    		((EditText) findViewById(R.id.login)).setText("");
        	((EditText) findViewById(R.id.password)).setText("");
        	
        	savePreferences();
    	} else {
    		isServerListEmpty();
    	}
    }
    
    private void dismissTaskDialog() {
    	Log.v(LOG_TAG, "dismissTaskDialog()");
    	if (mConnectDialog != null) {
    		mConnectDialog.dismiss();
    		mConnectDialog = null;
    	}
    }
    
    public void connectOnClick(View view) {
    	final SQLiteCursor cursor = (SQLiteCursor) mSpinner.getSelectedItem();
    	if (cursor == null) {
    		//Teoricamente nunca entra aqui. Vou logar pra facilitar o debug
    		Log.e(LOG_TAG, "ERRO! Não era pra entrar aqui NUNCA!");
    		Toast.makeText(this, "Selecione o servidor ao qual conectar. Utilize o menu para adicionar servidores.", Toast.LENGTH_LONG).show();
    		
    	} else {
        	// Start background task.
    		startManagingCursor(cursor);
    		currentServer = cursor.getPosition();
        	serverName = cursor.getString(cursor.getColumnIndex(ServersDbAdapter.KEY_NAME));
        	serverUrl = cursor.getString(cursor.getColumnIndex(ServersDbAdapter.KEY_URL)) + GW_WEBAPP_SUFIX;
        	serverXmlUrl = serverUrl + GW_APPDATA_SUFIX;
        	usernames[currentServer] = ((EditText) findViewById(R.id.login)).getText().toString();
        	passwords[currentServer] = ((EditText) findViewById(R.id.password)).getText().toString();
        	
        	if("".equals(usernames[currentServer]) || "".equals(passwords[currentServer])){
        		Toast.makeText(this, "Nome de usuário e/ou senha em branco.\nPreencha os campos e tente novamente.", Toast.LENGTH_LONG).show();
        		return;
        	}
			
        	final ProgressDialog dialog;
    		dialog = new ProgressDialog(GroupwareClient.this);
    		dialog.setTitle("Conectando a\n" + serverName);
	    	dialog.setCancelable(true);
	    	dialog.setOnCancelListener(new OnCancelListener(){
	    		@Override
	    		public void onCancel(DialogInterface dialog) {
	    			dismissTaskDialog();
	    			Toast.makeText(GroupwareClient.this, "Conexão interrompida pelo usuário.", Toast.LENGTH_LONG).show();
	    			try{
	    				mLoginConnection.unregisterAll();
	    			}catch(Exception e){
	    				Log.e(LOG_TAG, "Erro ao cancelar a conexão.");
	    			}
	    		}
	    	});
			dialog.setIndeterminate(true);
			dialog.show();
			dialog.setMessage("Conectando ao servidor");
			mConnectDialog = dialog;
			
			try{
				xmlRequestCode = ConnectionServiceConnection.generateId();
				mLoginConnection.registerGetRequest(serverXmlUrl, xmlRequestCode, false);
			}catch(Exception e){
				Log.e(LOG_TAG, "Não consegui pedir o XML de aplicação.", e);
			}
    	}
    }
    
    private void savePreferences(){
    	StringBuilder users = new StringBuilder();
    	StringBuilder passes = new StringBuilder();
    	for(int i=0; i<usernames.length; i++){
    		users.append("\b" + (usernames[i] == null ? "" : usernames[i]));
    		passes.append("\b" + (passwords[i] == null ? "" : passwords[i]));
    	}
    	//Remove initial \b character
    	String mergedUsers = users.toString().substring(1);
    	String mergedPasses = passes.toString().substring(1);
    	
    	//Save settings
    	Log.d(LOG_TAG, "Saving users: " + mergedUsers + ", passes: " + mergedPasses + " and currentServer: " + currentServer);
    	Log.d(LOG_TAG, "Total preferences: " + prefs.getAll().size());
    	prefs.edit()
    		.putString(PREFERENCE_USERNAME, mergedUsers)
    		.putString(PREFERENCE_PASSWORD, mergedPasses)
    		.putInt(PREFERENCE_SERVER, currentServer)
    		.commit();
    }

    private void onTaskCompleted() {
    	Log.v(LOG_TAG, "onTaskCompleted()");
    	
    	savePreferences();
    	
    	Bundle extras = new Bundle();
    	
		extras.putString(Component.EXTRA_APPLICATION_XML, appXml);
		extras.putString(Component.EXTRA_APPLICATION_URL, serverUrl);
    	
    	dismissTaskDialog();

    	// Start MainActivity.
    	final Intent intent = new Intent();
    	intent.setClass(this, ComponentDownloader.class);
    	intent.putExtras(extras);
    	startActivity(intent);
    }
    
    private ConnectionServiceConnection mLoginConnection = new ConnectionServiceConnection(GroupwareClient.class.getName()) {
    	private static final String LOG_TAG = "LoginConnection";
    	private static final String MAGIC_STRING = "login.css";
    	
    	private void toast(String text, int duration){
    		Message msg = gcHandler.obtainMessage(MSG_DISMISS_DIALOG);
    		gcHandler.sendMessage(msg);
    		
    		//O 0 é filler, porque eu uso o arg1 mas não uso o arg2, mas uso o what e o obj.
    		msg = gcHandler.obtainMessage(MSG_TOAST, duration, 0, text);
    		gcHandler.sendMessage(msg);
    	}
    	
    	private void dialog(String text){
    		Message msg = gcHandler.obtainMessage(MSG_UPDATE_DIALOG, text);
    		gcHandler.sendMessage(msg);
    	}
    	
		@Override
		public void onServiceConnected(){
			/* do nothing */
		}
		
		@Override
		public void onRequestFinished(String id, String filePath) {
			//Verifica se o usuário não cancelou a requisição
			if(mConnectDialog == null){
				//Requisição cancelada pelo usuário
				return;
			}
			
			File downloadedFile = new File(filePath);
			
			//Baixou o XML
			if (id.equals(xmlRequestCode)) {
				try {
					
					final InputStream source = new FileInputStream(downloadedFile);
					final StringWriter writer = new StringWriter();
					IOUtils.copy(source, writer);
					appXml = writer.toString();
					
					if(appXml.startsWith(ConnectionService.MAGIC_ERROR_STRING)){
						dismissTaskDialog();

						toast("Não foi possível contactar " + serverName + "! Verifique o endereço do servidor e sua conexão com a internet e tente novamente.", Toast.LENGTH_LONG);
						return;
					}
				
					ApplicationConfigurationParser acp = new ApplicationConfigurationParser();
					acp.parse(new ByteArrayInputStream(appXml.getBytes()));
					if(!acp.isValid()){
						throw new ConnectException("Formato inválido. O site não é um servidor GW?");
					}
					mLoginConnection.setLoginData(serverUrl + acp.getLoginUrl(), usernames[currentServer], passwords[currentServer]);
					//Valida login testando a url do repositório
					loginValidatorRequestCode = ConnectionServiceConnection.generateId();
					mLoginConnection.registerGetRequest(serverUrl + acp.getRepository(), loginValidatorRequestCode, false);
					dialog("Efetuando autenticação");
				
				} catch(Exception e) {
					Log.e(LOG_TAG, "Erro na leitura do XML do arquivo para a memória.", e);
					try {
						unregisterAll();
					} catch (Exception e1) {
						e1.printStackTrace();
					}
					toast("O site informado não é compatível com o GW-Android!", Toast.LENGTH_LONG);
				}
			//Validação do login
			} else if (id.equals(loginValidatorRequestCode)) {
				try {
					final InputStream source = new FileInputStream(downloadedFile);
					final StringWriter writer = new StringWriter();
					IOUtils.copy(source, writer);
					if (writer.toString().contains(MAGIC_STRING)) {
						toast("Nome de usuário e/ou senha inválidos.", Toast.LENGTH_SHORT);
						mLoginConnection.unregisterAll();
						return;
					}
					
					//Login válido - finalizar a aplicação e inicializar o componentDownloader
					gcHandler.sendEmptyMessage(MSG_FINISH);

				} catch(Exception e) {
					Log.e(LOG_TAG, "Erro na validação do login.", e);
					toast("Foi detectado um problema de configuração em " + serverName + ". Contacte o administrador.", Toast.LENGTH_LONG);
				}
			
			} else {
				Log.e(LOG_TAG, "Erro, código de requisição " + id + " inválido.");
			}
		}
	};
    
    private void fillData() {
    	Cursor c = mDbAdapter.fetchAllServers();
    	startManagingCursor(c);
    	
    	// Create an array to specify the fields we want to display...
    	String[] from = new String[] { ServersDbAdapter.KEY_NAME, ServersDbAdapter.KEY_URL };
    	// ...and an array of the fields we want to bind those fields to
    	int[] to = new int[]{ R.id.server_name, R.id.server_url };

    	SimpleCursorAdapter serversAdapter = new SimpleCursorAdapter(this, R.layout.server_row, c, from, to);
    	serversAdapter.setDropDownViewResource(R.layout.server_row_dropdown);
    	mSpinner = (Spinner) findViewById(R.id.change_server);
    	mSpinner.setAdapter(serversAdapter);
    	mSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {

			@Override
			public void onItemSelected(AdapterView<?> dontCare1, View dontCare2, int pos, long dontCare3) {
				((EditText) findViewById(R.id.login)).setText(usernames[pos]);
	        	((EditText) findViewById(R.id.password)).setText(passwords[pos]);
			}

			@Override
			public void onNothingSelected(AdapterView<?> dontCare) {}
		});
    }
    
    private void refreshSpinner() {
    	Log.d(LOG_TAG, "Cursor refreshed!");
    	SimpleCursorAdapter adapter = (SimpleCursorAdapter) mSpinner.getAdapter();
		adapter.getCursor().requery();
		adapter.notifyDataSetChanged();
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
    	getMenuInflater().inflate(R.menu.servers_menu, menu);
    	return super.onCreateOptionsMenu(menu);
    }
    
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    	switch (item.getItemId()) {
		case R.id.server_add:
			// Inicia uma activity que cuida da adição de servidores.
			final Intent intent = new Intent(this, AddServerActivity.class);
			startActivityForResult(intent, ACTION_ADD_SERVER);
			creatingServer = true;
			return true;
		case R.id.server_remove:
			// Exibe um diálogo de confirmação antes de remover o servidor.
			final SQLiteCursor cursor = (SQLiteCursor) mSpinner.getSelectedItem();
			if (cursor == null) {
				Toast.makeText(this, "Nenhum servidor selecionado.", Toast.LENGTH_SHORT);
			} else {
				final String serverName = cursor.getString(cursor.getColumnIndex(ServersDbAdapter.KEY_NAME));
				final AlertDialog.Builder builder = new AlertDialog.Builder(this);
				builder.setTitle("Confirmação");
				builder.setMessage("Deseja remover o servidor \"" + serverName + "\"?");
				builder.setPositiveButton("Sim", new DialogInterface.OnClickListener() {
						@Override
						public void onClick(DialogInterface dialog, int which) {
							// Remove o servidor.
							final long rowId = cursor.getLong(cursor.getColumnIndex(ServersDbAdapter.KEY_ROWID));
							final int rowPosition = cursor.getPosition();
							mDbAdapter.deleteServer(rowId);
							refreshSpinner();
							
							usernames = removeFromArray(usernames, rowPosition);
							passwords = removeFromArray(passwords, rowPosition);
							if (currentServer == mSpinner.getCount()){
								currentServer--;
							}
				        	savePreferences();
				        	
							isServerListEmpty();
						}
					});
				builder.setNegativeButton("Não", new DialogInterface.OnClickListener() {
						@Override
						public void onClick(DialogInterface dialog, int which) {
							dialog.cancel();
						}
					});
				builder.create().show();
			}
			return true;
		default:
			return super.onOptionsItemSelected(item);
		}
    }
    
    private String[] removeFromArray(String[] array, int pos){
    	String[] newArray;
    	if (array.length == 1){
    		newArray = new String[1];
    		newArray[0] = "";
    	}else{
	    	newArray = new String[array.length-1];
	    	for(int i=0, j=0; i<array.length; i++){
	    		if(i != pos){
	    			newArray[j] = array[i];
	    			j++;
	    		}
	    	}
    	}
    	return newArray;
    }
    
    private String[] addToArray(String[] array, String newString){
    	String[] newArray = new String[array.length+1];
    	int i;
    	for(i=0; i<array.length; i++){
			newArray[i] = array[i];
    	}
    	newArray[i] = newString;
    	return newArray;
    }
}